/**
 * Package: vvg.lib
 * 
 * Base VVG libraries. Must load this file before app.js and other lib_*.js.
 * 
 * @author: King Lung Chiu
 */
(function(vvg){
   
   vvg.app = {
      debug: false
      ,credits: {
         title: "Visualising Victoria's Groundwater"
         ,text:
            '<span style="text-align: center;">' +
            '<p>Application developed by Victorian eResearch Strategic Initiative and GeoDataWiz, ' +
            'in partnership with the University of Ballarat, CeCC.</p>' +
            '<p>Data courtesy of Department of Primary Industries, ' +
            'Future Farming Systems Research Division.</p>' +
            '<p>Powered by SISS (weblink: http://siss.auscope.org)</p>' +
            '</span>'
         ,width: 360 // in pixels
      }
   };
        
   var lib = vvg.lib = {
      log: function(msg) {
         console.log(msg);
      }
      ,isArray: function(value) {
         return Object.prototype.toString.apply(value) === '[object Array]';
      }
      
      ,typeOf: function(value){
         if(lib.isArray(value)){
            return 'array';
         } else {
            return (typeof value);
         }
      }

      /**
       * Creates a new array from an existing array, with only items
       * that are 'unique' based on their values in a user-specified property.
       * The new array will be a shallow-copy of a subset of the orignal.
       * 
       * The selection is done by first making a shallow copy of the original
       * array, then optionally sorting the copy via Array.sort(), then
       * picking the first item for each unique property from the copy.
       * 
       * The sorting will be done either:
       *    1) Not at all, if a sortfield is not supplied, or
       *    2) In ascending order of a top-level sortfield, or
       *    3) In ascending order based on a supplied comparator,
       *       which should adhere to the requirements of Array.sort().
       *
       * Sample Usage:
       * 
       * a) Return the first item from originalArray for each unique id:
       *    var subsetArray = lib.unique(originalArray, 'id');
       * 
       * b) Return the first item from originalArray for each unique id nested
       *    under each item's 'attributes' property:
       *    var subsetArray = lib.unique(originalArray, function(x) {
       *       return x.attributes.id;
       *    });
       * 
       * c) Return the oldest item (by 'date') from originalArray for each
       *    unique id:
       *    var subsetArray = lib.unique(originalArray, 'id', 'date');
       * 
       * d) Return the latest item (by attributes.date) from originalArray for
       *    each unique id:
       *    var subsetArray = lib.unique(originalArray, 'id', function(x, y) {
       *       var d1 = x.attributes.date,
       *           d2 = y.attributes.date;
       *       return d1 === d2? 0: (d1 > d2? -1 : 1);
       *    });
       * 
       * @param: original
       *    The array from which to select 'unique' items. This array will not
       *    be modified.
       * @param: uniqueProperty
       *    The property in each array item to use for determining uniqueness.
       *    Can be either:
       *    a) The name of the unique top-level property as a String, or
       *    b) An extractor function that accepts an item from the array and
       *       returns the required unique property,
       *       eg. from within a nested property.
       * @param: sortfield
       *    Optional parameter.
       *    Specifies the property in each array item to use during sorting.
       *    Can be either:
       *    a) Not supplied, in which case no sorting will be performed, or
       *    b) The name of the top-level property to use during sorting, or
       *    c) A comparator function to use during sorting. Supply this if you
       *       want to enable sorting, but have the following constraints:
       *       i) The sort field is not a top-level property on each item, or
       *       ii) you need to sort in descending order.
       *
       * @returns:
       *    A shallow copy of a subset of the orignal array, containing only
       *    unique items.
       */
      ,unique: function(original, uniqueProperty, sortfield) {
         var extractProperty = typeof uniqueProperty === 'function'? uniqueProperty : function(x) {
            return x[uniqueProperty];
         };
         var comparator = typeof sortfield === 'function'?
            sortfield :
            (typeof sortfield === 'string'? lib.comparator_Asc(sortfield): null);
         var copy = comparator === null?
            original.slice(0) : original.slice(0).sort(comparator);
         var uniqueList = [];
         var selected = {};
         var item = null;
         var value = null;
         var i = 0;
         var len = copy.length;
         
         for(i = 0; i < len; i++) {
            item = copy[i];
            value = extractProperty(item);
            if(selected[value] !== true) {
               uniqueList.push(item);
               selected[value] = true;
            }
         }
         return uniqueList;
      }
      
      /**
       * Creates a new array from an existing array, with items that pass the
       * conditions set by a predicate.
       * 
       * @param: originalList
       *    The original array from which to select items.
       * @param: predicate
       *    A function that takes an item and returns true if the item should be
       *    selected, and false if not.
       *  
       * @returns:
       *    A new array with items that passed the supplied predicate.
       */
      ,filter: function(originalList, predicate) {
         if(!lib.isArray(originalList) || typeof predicate !== 'function') {
            throw {
              name: 'IllegalArgumentException'
              ,message: 'vvb.lib.filter(): originalList must be an array, predicate must be a function'
            };
         }         
         var filteredList = [];
         var i = 0;
         var len =  originalList.length;
         
         for(i = 0; i < len; i++) {
            if(predicate(originalList[i]) === true) {
               filteredList.push(originalList[i]);
            }
         }
         
         return filteredList;
      }
      
      /**
       * Returns, from an existing array, the first item that satisfies a given
       * predicate.
       * 
       * @param: originalList
       *    The original array from which to find an item.
       * @param: predicate
       *    A function that takes an item and returns true if the item should be
       *    selected, and false if not.
       *  
       * @returns:
       *    The first item from orignalList that satisfies the predicate,
       *    or null if none found.
       */
      ,find: function(originalList, predicate) {
         if(!lib.isArray(originalList) || typeof predicate !== 'function') {
            throw {
              name: 'IllegalArgumentException'
              ,message: 'vvb.lib.find(): originalList must be an array, predicate must be a function'
            };
         }         
         var i = 0;
         var len =  originalList.length;
         
         for(i = 0; i < len; i++) {
            if(predicate(originalList[i]) === true) {
               return originalList[i];
            }
         }
         
         return null;
      }
      
      /**
       * Creates a comparator function for sorting, in ascending order, by a
       * user-specified property.
       * 
       * @param: sortField
       *    The property to compare.
       *    Can be either:
       *    a) The name of the property as a String, or
       *    b) An extractor function that accepts an item and returns the
       *    required sort field, eg. from within a nested property.
       */
      ,comparator_Asc: function(sortField) {
         var extractProperty = typeof sortField === 'function'? sortField : function(x) {
            return x[sortField];
         };
         return function(x, y) {
            var a = extractProperty(x);
            var b = extractProperty(y);
            return a === b? 0 : (a < b? -1 : 1);
         };
      }
      /**
       * Creates a comparator function for sorting, in descending order, by a
       * user-specified property.
       * 
       * @param: sortField
       *    The property to compare.
       *    Can be either:
       *    a) The name of the property as a String, or
       *    b) An extractor function that accepts an item and returns the
       *    required sort field, eg. from within a nested property.
       */
      ,comparator_Desc: function(sortField) {
         var compareAsc = lib.comparator_Asc(sortField);
         return function(x, y) {
            return -1 * compareAsc(x, y);
         };
      }
     
      /**
       * Custom event types fired by the map component.
       */
      ,eventNames: {
         // fired when a new set of data is ready for visualisation
         VIZ_DATA_READY: 'vvg_vizDataReady'

         // fired when the map begins to fetch a new set of data from the server
         ,VIZ_DATA_FETCH_STARTED: 'vvg_vizDataFetchStarted'

         // fired when the map finishes fetching a new set of data from the server
         ,VIZ_DATA_FETCH_COMPLETE: 'vvg_vizDataFetchComplete'

         // fired when a bore is selected on the chart
         ,VIZ_BORE_SELECTED: 'vvg_vizBoreSelected'

         // fired when a bore is unselected on the chart
         ,VIZ_BORE_UNSELECTED: 'vvg_vizBoreUnelected'
      }
      
      /**
       * Helper method for creating an object with a given key & value.
       * Use this when you need to make an object but can't directly use
       * an object literal, eg. when key comes from a variable.
       * 
       * Used internally when creating event handler objects that listen for
       * one of the events under vvg.lib.eventNames.
       */
      ,newObj: function(key, value) {
         var obj = {};
         obj[key] = value;
         return obj;
      }
      
      /**
       * Helper method for creating an object from a given set of key-value
       * pairs. Use this when you need to make an object but can't directly use
       * an object literal, eg. when keys come from variables.
       * 
       * Used internally when creating event handler objects that listen for
       * events specified under vvg.lib.eventNames.
       * 
       * @param: kvPairs
       *    An array of key-value pairs to construct an object from.
       *    eg. the following:
       *
       *       var myObj = lib.kvPairs([
       *          ['a', 'value for property a']
       *          ,['b', 'value for property b']
       *          ,['z', 'value for property z']
       *       ]);
       *    
       *    has the same effect as this object literal:
       *    
       *       var myObj = {
       *          a: 'value for property a'
       *          ,b: 'value for property b'
       *          ,z: 'value for property z'
       *       };
       */
      ,objFromKVPairs: function(kvPairs) {
         var obj = {};
         var kv = null;
         var len = kvPairs.length;
         var i = 0;
         if(lib.isArray(kvPairs)){
            for(i = 0; i < len; i++) {
               kv = kvPairs[i];
               if(lib.isArray(kv)){
                  obj[kv[0]] = kv[1];
               }
            }
         }
         return obj;
      }
   };

}(window.vvg = window.vvg || {}));